package de.fru;

import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import de.fru.ByteStream.Constant;

public class Assembler {

  public static List<Fragment> read(InputStream is) throws Exception{
    ByteStream reader = new ByteStream(is);
    parseClass(reader);
    return null;
  }
  
  @SuppressWarnings("unused")
private static void parseClass( ByteStream reader ) throws Exception{    
    //PARSE: Magic number
    int[] array = new int[]{0xCA, 0xFE, 0xBA, 0xBE};
     
    for(int a : array){
        int next = reader.findNext();
        if(a != next)throw reader. new BytecodeException("Found [%s] but expected [%s] as in [%s]", next, a, array );
      }
    
    //PARSE: Version
    int minorVersion = reader.findShort();
    System.out.println(String.format("Minor version [%s]", minorVersion ));
    
    int majorVersion = reader.findShort();
    System.out.println(String.format("Mayor version [%s]", majorVersion ));
    
    //PARSE: Constants Table
    int constantPoolCount = reader.findShort();
    Map<Integer, Constant> constants = new HashMap<Integer, Constant>();
    
    System.out.println(String.format("Number of constants found [%s]",constantPoolCount ));
    
    for(int c = 1; c < constantPoolCount; c++){
      int byteType = reader.findNext();
      ByteStream.Type type = null;
      Constant constant = null;
      switch(byteType){
        case 1: //UTF-8
          type = ByteStream.Type.Utf8;
          byte[] bytes = new byte[reader.findShort()];
          for(int i = 0; i < bytes.length; i++){
            bytes[i] = (byte)reader.findNext();
          }
          constant = new Constant(new String(bytes, "UTF-8"));
          break;
        case 3: //Integer
          type = ByteStream.Type.Integer;
          constant = new Constant(""+reader.findInt());
          break;
        case 4: //Float
          type = ByteStream.Type.Float;
          constant = new Constant(""+Float.intBitsToFloat( reader.findInt() ));
          break;
        case 5: //Long
          type = ByteStream.Type.Long;
          constant = new Constant(""+reader.findLong());
          break;
        case 6: //Double
          type = ByteStream.Type.Double;
          constant = new Constant(""+Double.longBitsToDouble( reader.findLong() ));
          break;
        case 9: //FieldRef
          type=ByteStream.Type.FieldRef;
          constant = new Constant(reader.findShort(),reader.findShort());
          break;
        case 10: //MethodRef
          type=ByteStream.Type.MethodRef;
          constant = new Constant(reader.findShort(),reader.findShort());
          break;
        case 11: //InterfaceMethodRef
          type=ByteStream.Type.InterfaceMethodRef;
          constant = new Constant(reader.findShort(),reader.findShort());
          break;
        case 12: //NameAndType
          type=ByteStream.Type.NameAndType;
          constant = new Constant(reader.findShort(),reader.findShort());
          break;
        case 7: //Class
          type=ByteStream.Type.Class;
          constant = new Constant(reader.findShort());
          break;
        case 8: //String
          type=ByteStream.Type.String;
          constant = new Constant(reader.findShort());
          break;
        default:
          throw reader.new BytecodeException("Found [%s] but expected any of [01,03,04,05,06,07,08,09,0A,0B,0C]",byteType);
      }
      constant.setType(type);
      System.out.println("#"+c+" "+constant);
      constants.put( c, constant);
      if( type ==  ByteStream.Type.Long || type == ByteStream.Type.Double ){
        c++; //ick
      }
    }
    
    //PARSE: Class Information
    int accessFlags = reader.findShort();
    System.out.println(String.format("Class access flag  [%s]", accessFlags ));
    
    int thisClass = reader.findShort();
    System.out.println(String.format("Class this [%s]", thisClass ));
    
    int superClass = reader.findShort();
    System.out.println(String.format("Class super [%s]", superClass ));
    
    int interfaceCount = reader.findShort();
    int[] interfaces = new int[interfaceCount];
    for(int i = 0; i < interfaces.length; i++){
      interfaces[i] = reader.findShort();
      System.out.println(String.format("Class has interface [%s]", interfaces[i] ));
    }
    
    //PARSE: Fields
    int fieldCount = reader.findShort();
    for(int i = 0; i < fieldCount; i++){
      int fieldFlag = reader.findShort();
      int fieldName = reader.findShort();
      int fieldType = reader.findShort();
      System.out.println(String.format("Found field [%s] of type [%s]",fieldName, fieldType));
      findAttributes(reader, constants);
    }
   
    //PARSE: Methods
    int methodCount = reader.findShort();
    for(int i = 0; i < methodCount; i++){
      int methodFlag = reader.findShort();
      int methodName = reader.findShort();
      int methodType = reader.findShort();
      System.out.println(String.format("Found method [%s] of type [%s]",methodName, methodType));
      findAttributes(reader, constants);
    }
    
    //PARSE: Info 
    findAttributes(reader, constants);
    
    if(reader.input.read() != -1){ //The end should be reached
      throw reader.new BytecodeException( "Expected end of file" );
    }
  }
  
  private static void findAttributes(ByteStream reader, Map<Integer, Constant> constants){
    int attributeCount = reader.findShort();
    for(int i = 0; i < attributeCount; i++){
      int name = reader.findShort();
      int size = reader.findInt(); //attribute length
      if("code".equalsIgnoreCase( constants.get( name ).value)){
        parseCode(reader, constants);
      }else{
        System.out.println("Found attribute ["+name+"]");
        
        int[] bytes = new int[size];
        for(int j = 0; j < bytes.length; j++){
          bytes[j] = reader.findNext();
        }
      } 
    }
  }
  
  
  private static void parseCode(ByteStream reader, Map<Integer, Constant> constants){
    int maxStack = reader.findShort();
    int maxLocal = reader.findShort();
    System.out.println("Max Stack ["+maxStack+"] and Local ["+maxLocal+"]");
    
    ByteStream codeReader = new ByteStream(reader,reader.findInt());
    Instruction.parseInstructions(codeReader);
    
    int tablelength = reader.findShort();
    
    for(int i = 0; i < tablelength; i++){
      int from = reader.findShort();
      int to = reader.findShort();
      int jump = reader.findShort();
      int exception = reader.findShort();
      if(exception == 0)System.out.println(String.format( "Handle all exception from [%s] to [%s] then jump to [%S]",from,to,jump ));
      else System.out.println(String.format( "Handle exception [%s] from [%s] to [%s] then jump to [%S]", exception,from,to,jump ));
    }

    findAttributes( reader, constants );
  }
}
